package  com.trg.flex.dp
{
	import com.trg.flex.FlexUtils;
	
	import mx.collections.Sort;
	import mx.utils.ObjectUtil;
	
	public class NestedSort extends Sort
	{
		public function NestedSort(sort:Sort=null)
		{
			super();
			//when the insance is fresh compareFunction is always internalCompare
			//we need to save a reference to this function for later
			_internalCompare = compareFunction;
			
			if (sort != null) { //copy constructor
				//only if the compareFunction on the sort is not its internalCompare
				//should we copy that compareFunction to this Sort
				var originalCompare:Function = sort.compareFunction;
				sort.compareFunction == null;
				if (sort.compareFunction !== originalCompare) {
					sort.compareFunction = originalCompare;
					this.compareFunction = originalCompare;
				}
				this.fields = sort.fields;
				this.unique = sort.unique;
			}
		}
		
		private var chains:Array;
		private var _compareFunction:Function;
		private var _internalCompare:Function;
		
		private function prepareSort():void {
			if (fields == null || fields.length == 0) {
				_compareFunction = null;
			} else {
				var anyNested:Boolean = false;
				chains = [];
				for (var i:int = 0; i < fields.length; i++) {
					chains[i] = FlexUtils.getChain(fields[i].name);
					if (chains[i] != null && chains[i].length > 1)
						anyNested = true;
				}
				if (anyNested == false) {
					_compareFunction = null;
				} else if (fields.length == 1) {
					chains = chains[0];
					_compareFunction = compareSingle;
				} else {
					_compareFunction = compareMulti;
				}
			}
		}
		
		override public function sort(items:Array):void {
			prepareSort();
            
            var usingCustomCompareFunction:Boolean = this.compareFunction !== _internalCompare;
			if (!usingCustomCompareFunction) this.compareFunction = _compareFunction;
			super.sort(items);
			if (!usingCustomCompareFunction) this.compareFunction = null;
		}
		
		override public function findItem(items:Array, values:Object, mode:String,
				returnInsertionIndex:Boolean=false, compareFunction:Function=null):int {
			prepareSort();
			
			var values2:Object = {};
			for (var i:int = 0; i < fields.length; i++) {
				var fieldName:String = fields[i].name;
				values2[fieldName] = FlexUtils.getNestedProperty(values, fieldName); 
			}
			
            var usingCustomCompareFunction:Boolean = this.compareFunction !== _internalCompare;
			if (!usingCustomCompareFunction) this.compareFunction = _compareFunction;
			var result:int = super.findItem(items, values2, mode, returnInsertionIndex, compareFunction);
			if (!usingCustomCompareFunction) this.compareFunction = null;
			return result;
		}
		
		private function compareSingle(a:Object, b:Object, p_fields:Array=null):int {
			var result:int = nestedCompareFunction(a, b, chains);
			if (fields[0].descending) result *= -1;
			return result;
		}
		
		private function compareMulti(a:Object, b:Object, p_fields:Array=null):int {
			for (var i:int = 0; i < fields.length; i++) {
				var result:int = nestedCompareFunction(a, b, chains[i]);
				if (result != 0) {
					if (fields[i].descending) result *= -1;
					return result;
				}
			}
			return 0;
		}
		
		private function nestedCompareFunction(a:Object, b:Object, chain:Array):int {
	    	a = FlexUtils.getChainedProperty(a, chain);
	    	b = FlexUtils.getChainedProperty(b, chain);
	    	
	    	if (a is Number || b is Number)
	    		return ObjectUtil.numericCompare(a as Number, b as Number);
	    	else {
	    		if (a != null && !(a is String) ) a = a.toString()
	    		if (b != null && !(b is String) ) b = b.toString()
	    		return ObjectUtil.stringCompare(a as String, b as String);
	    	}
	    }
	}
}